/* ***** BEGIN LICENSE BLOCK *****
 Version: MPL 1.1

 The contents of this file are subject to the Mozilla Public License Version
 1.1 (the "License"); you may not use this file except in compliance with
 the License. You may obtain a copy of the License at
 http://www.mozilla.org/MPL/

 Software distributed under the License is distributed on an "AS IS" basis,
 WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 for the specific language governing rights and limitations under the
 License.

 The Original Code is JHEAD (http://www.sentex.net/~mwandel/jhead/).

 The Initial Developer of the Original Code is
 Matthias Wandel.
 Portions created by the Initial Developer are Copyright (C) 2005
 the Initial Developer. All Rights Reserved.

 Contributor(s):
    Ted Mielczarek <luser_mozilla@perilith.com>
    Christian Eyrich <ch.ey@gmx.net>
    Julen Ruiz Aizpuru <julenx@gmail.com>
 ***** END LICENSE BLOCK ***** */

/*
 *  Interpreter for binary EXIF data.
 */

const ggExif = {
  // data formats
  FMT_BYTE       : 1,
  FMT_STRING     : 2,
  FMT_USHORT     : 3,
  FMT_ULONG      : 4,
  FMT_URATIONAL  : 5,
  FMT_SBYTE      : 6,
  FMT_UNDEFINED  : 7,
  FMT_SSHORT     : 8,
  FMT_SLONG      : 9,
  FMT_SRATIONAL  : 10,
  FMT_SINGLE     : 11,
  FMT_DOUBLE     : 12,

  // EXIF tags
  TAG_GPSINFO        : 0x8825,
  // We just need GPS tags for now
  TAG_GPS_LAT_REF    : 1,
  TAG_GPS_LAT        : 2,
  TAG_GPS_LON_REF    : 3,
  TAG_GPS_LON        : 4,
  TAG_GPS_ALT_REF    : 5,
  TAG_GPS_ALT        : 6,

  BytesPerFormat: [0,1,1,2,4,8,1,1,2,4,8,4,8],


  dir_entry_addr:
    function (start, entry) {
      return start + 2 + 12*entry;
    },

  ConvertAnyFormat:
    function (data, format, offset, numbytes, swapbytes) {
      var value = 0;

      switch(format) {
        case this.FMT_STRING:
        case this.FMT_UNDEFINED: // treat as string
          value = ggExifUtils.bytesToString(data, offset, numbytes);
          // strip trailing whitespace
          value = value.replace(/\s+$/, '');
        break;

        case this.FMT_SBYTE:
          value = data[offset];
        break;
        case this.FMT_BYTE:
          value = data[offset];
        break;

        case this.FMT_USHORT:
          value = ggExifUtils.read16(data, offset, swapbytes);
        break;
        case this.FMT_ULONG:
          value = ggExifUtils.read32(data, offset, swapbytes);
        break;

        case this.FMT_URATIONAL:
        case this.FMT_SRATIONAL: {
          var Num,Den;
          Num = ggExifUtils.read32(data, offset, swapbytes);
          Den = ggExifUtils.read32(data, offset+4, swapbytes);
          if (Den == 0){
            value = 0;
          } else {
            value = Num/Den;
          }
        break;
        }

        case this.FMT_SSHORT:
          Value = ggExifUtils.read16(data, offset, swapbytes);
        break;
        case this.FMT_SLONG:
          Value = ggExifUtils.read32(data, offset, swapbytes);
        break;

        // ignore, probably never used
        case this.FMT_SINGLE:
          value = 0;
        break;
        case this.FMT_DOUBLE:
          value = 0;
        break;
      }
      return value;
    },

  readGPSDir:
    function (dataObj, data, dirstart, swapbytes) {
      var numEntries = ggExifUtils.read16(data, dirstart, swapbytes);
      var gpsLatHemisphere = 'N', gpsLonHemisphere = 'E', gpsAltReference = 0;
      var gpsLat, gpsLon, gpsAlt;
      var vals = new Array();

      for (var i=0; i<numEntries; i++) {
        var entry = this.dir_entry_addr(dirstart, i);
        var tag = ggExifUtils.read16(data, entry, swapbytes);
        var format = ggExifUtils.read16(data, entry+2, swapbytes);
        var components = ggExifUtils.read32(data, entry+4, swapbytes);

        if (format >= this.BytesPerFormat.length)
          continue;

        var nbytes = components * this.BytesPerFormat[format];
        var valueoffset;

        if (nbytes <= 4) // stored in the entry
          valueoffset = entry + 8;
        else
          valueoffset = ggExifUtils.read32(data, entry + 8, swapbytes);

        var val = this.ConvertAnyFormat(data, format, valueoffset, nbytes, swapbytes);

        switch(tag) {
          case this.TAG_GPS_LAT_REF:
            gpsLatHemisphere = val;
          break;

          case this.TAG_GPS_LON_REF:
            gpsLonHemisphere = val;
          break;

          case this.TAG_GPS_ALT_REF:
            gpsAltReference = val;
          break;

          case this.TAG_GPS_LAT:
          case this.TAG_GPS_LON:
            // data is saved as three 64bit rationals -> 24 bytes
            // so we've to do another two ConvertAnyFormat() ourself
            // e.g. 0x0b / 0x01, 0x07 / 0x01, 0x011c4d / 0x0c92
            // but can also be only 0x31 / 0x01, 0x3d8ba / 0x2710, 0x0 / 0x01
            var gpsval = val * 3600;
            gpsval += this.ConvertAnyFormat(data, format, valueoffset+8, nbytes, swapbytes) * 60;
            gpsval += this.ConvertAnyFormat(data, format, valueoffset+16, nbytes, swapbytes);
            vals[tag] = gpsval;
          break;

          case this.TAG_GPS_ALT:
            vals[tag] = val;
          break;

          default:
          break;
        }
      }

      // use dd format by default
      var degFormatter = ggExifUtils.dd2dd;
      // now output all existing values
      if (vals[this.TAG_GPS_LAT] != undefined) {
        var gpsArr = degFormatter(vals[this.TAG_GPS_LAT]);
        gpsArr.push(gpsLatHemisphere);
        //dataObj.GPSLat = stringBundle.getFormattedString("latlon"+degFormat, gpsArr);
      }
      if (vals[this.TAG_GPS_LON] != undefined) {
        var gpsArr = degFormatter(vals[this.TAG_GPS_LON]);
        gpsArr.push(gpsLonHemisphere);
        //dataObj.GPSLon = stringBundle.getFormattedString("latlon"+degFormat, gpsArr);
      }
      /* GPS Altitude not supported by GureGipuzkoa
      if (vals[this.TAG_GPS_ALT] != undefined) {
        dataObj.GPSAlt = [vals[this.TAG_GPS_ALT] * (gpsAltReference ? -1.0 : 1.0)];
      }*/

      // Get the straight decimal values without rounding.
      // For creating links to map services.
      if (vals[this.TAG_GPS_LAT] != undefined &&
          vals[this.TAG_GPS_LON] != undefined) {
        dataObj.GPSPureDdLat = vals[this.TAG_GPS_LAT] / 3600 * (gpsLatHemisphere == 'N' ? 1.0 : -1.0);
        dataObj.GPSPureDdLon = vals[this.TAG_GPS_LON] / 3600 * (gpsLonHemisphere == 'E' ? 1.0 : -1.0);
      }
    },


  /* Reads the actual EXIF tags.
     Also extracts tags for textual informations like
     By, Caption, Headline, Copyright.
     But doesn't overwrite those fields when already populated
     by IPTC-NAA or IPTC4XMP.
  */
  readExifDir:
    function (dataObj, data, dirstart, swapbytes) {
      var ntags = 0;
      var numEntries = ggExifUtils.read16(data, dirstart, swapbytes);
      var interopIndex = "";
      var colorSpace = 0;
      var dateTime = 0;
      var dateTimeOrig = 0;

      for (var i=0; i<numEntries; i++) {
        var entry = this.dir_entry_addr(dirstart, i);
        var tag = ggExifUtils.read16(data, entry, swapbytes);
        var format = ggExifUtils.read16(data, entry+2, swapbytes);
        var components = ggExifUtils.read32(data, entry+4, swapbytes);

        if (format >= this.BytesPerFormat.length)
          continue;

        var nbytes = components * this.BytesPerFormat[format];
        var valueoffset;

        if (nbytes <= 4) { // stored in the entry
          valueoffset = entry + 8;
        }
        else {
          valueoffset = ggExifUtils.read32(data, entry + 8, swapbytes);
        }

        var val = this.ConvertAnyFormat(data, format, valueoffset, nbytes, swapbytes);

        ntags++;
        switch(tag) {
          case this.TAG_GPSINFO:
            this.readGPSDir(dataObj, data, val, swapbytes);
          break;

          default:
            ntags--;
        }
      }

      return ntags;
    },

}
